#include <fstream>
#include <sstream>
#include <algorithm>
#include <numeric>
#include <stdexcept>
#include <vector>
#include <deque>
#include <string>
#include <iostream>
#include <iomanip>

//#include <unistd.h>
//#include <sys/types.h>
//#include <sys/stat.h>

#include <boost/foreach.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/algorithm/string.hpp>


#define DEBUG_LEVEL 6
#include <debug_func.h>
#include <portable_timer.h>
#include <progress_indicator.h>
#include <index_string.h>
#define _MY_STL_MACROS
#include <stlhelper.h>
#include <ultoa.h>
#include <print_error.h>
#include <perlutil.h>
#include <open_fstream.h>
#include <get_filename.h>
#include <seq_pairs.h>
#include <tab_delimited_file_to_map.h>

#include "get_arg.h"

using std::istringstream;
using std::vector;
using std::deque;
using std::ofstream;
using std::ifstream;
using std::istream;
using std::ostream;
using std::string;
using std::runtime_error;
using std::cerr;
using std::cin;
using std::cout;
typedef t_identifier_map::t_1_to_m t_1_to_m;



//________________________________________________________________________________________

//		do_substitute

//________________________________________________________________________________________
struct do_substitute
{
	t_index_string&				ids_from;
	t_index_string&				ids_to;
	deque<t_1_to_m>&			lookup_1_to_m;
	deque<unsigned>&			many_elements;
	bool						keep_non_matching_lines;
	t_index_string&				missing_identifiers;
	t_program_args&				args;
	std::string					comment_letters;
	unsigned					cnt_ignored_entries;
	unsigned					cnt_pairs_output;
	unsigned					cnt_pairs_input;
	do_substitute(	t_index_string&				ids_from_,
					t_index_string&				ids_to_,
					deque<t_1_to_m>&			lookup_1_to_m_,
					deque<unsigned>&			many_elements_,
					bool						keep_non_matching_lines_,
					t_index_string&				missing_identifiers_,
					t_program_args&				args_,
					const std::string&			comment_letters_):
						ids_from(ids_from_),
						ids_to(ids_to_),
						lookup_1_to_m(lookup_1_to_m_),
						many_elements(many_elements_),
						keep_non_matching_lines(keep_non_matching_lines_),
						missing_identifiers(missing_identifiers_),
						args(args_),
						comment_letters(comment_letters_),
						cnt_ignored_entries(0),
						cnt_pairs_output(0),
						cnt_pairs_input(0)
						{}
	void operator()(istream& is, unsigned cnt_line)
	{
		// get data for one protein pair
		string name1, name2;

		// comment if false
		string comment_line;
		if (!seq_pairs::parse_ids(is, name1, name2, comment_line, comment_letters,
							  seq_pairs::ePARSE_IDS_ONLY))
		{
			if (comment_line[0] != '\n')
				cout << comment_line;
			return;
		}


		string line;
		if (is.peek() == '\t')
		{
			getline(is, line);
			is.putback('\n');
		}

		++cnt_pairs_input;
		
		unsigned old_id1, old_id2;
		if (!ids_from.is_indexed(name1, old_id1) ||
			!ids_from.is_indexed(name2, old_id2) ||
			lookup_1_to_m[old_id1].offset ==
			lookup_1_to_m[old_id1].end			||
			lookup_1_to_m[old_id2].offset ==
			lookup_1_to_m[old_id2].end			)
		{
			if (!ids_from.is_indexed(name1))
				missing_identifiers.index(name1);
			if (!ids_from.is_indexed(name2))
				missing_identifiers.index(name2);
			if (keep_non_matching_lines)
			{
				cout	<< name1 << "\t"
						<< name2;
				cout << line;
				cout << "\n";
				++cnt_pairs_output;
			}
			// log error
			return;
		}



		
		assert(old_id1 < lookup_1_to_m.size());
		assert(old_id2 < lookup_1_to_m.size());
		for (unsigned ii = lookup_1_to_m[old_id1].offset, 
					endii = lookup_1_to_m[old_id1].end; ii != endii; ++ii)
		{
			for (unsigned jj = lookup_1_to_m[old_id2].offset, 
						endjj = lookup_1_to_m[old_id2].end; jj != endjj; ++jj)
			{
				//DEBUG
				assert(many_elements[ii] < ids_to.size());
				assert(many_elements[jj] < ids_to.size());
				// log error if identical
				cout	<< ids_to[many_elements[ii]] << "\t"
						<< ids_to[many_elements[jj]];
				cout << line;
				cout << "\n";
				++cnt_pairs_output;
			}
		}
		
	}
};








//________________________________________________________________________________________

//		substitute_ids_from_file

//			do actual mapping

//________________________________________________________________________________________
inline string is_plural(unsigned i)
{
	return i > 1 ? "s" : "";
}
void substitute_ids_from_file(  	t_index_string&		ids_from,
									t_index_string&		ids_to,
									deque<t_1_to_m>&   	lookup_1_to_m,
									deque<unsigned>&   	many_elements,
									bool				keep_non_matching_lines,
									ofstream&			f_unmatched_identifiers,
									t_program_args& 	args)
{
	debug_func_cerr(5);

	string file_name = args.file_input.length() ? 
						 get_filename(args.file_input) : 
						 "STDIN";
	args.VERBOSE() << "\tUpdating identifiers from ["
							<< file_name << "]...\n";
	t_index_string missing_identifiers;
	do_substitute parse_pairs(ids_from, ids_to, lookup_1_to_m, many_elements, 
								keep_non_matching_lines, missing_identifiers, 
									args, args.comment_letters);
	seq_pairs::read_data(parse_pairs, args.file_input, args.VERBOSE());
	if (missing_identifiers.size())
	{
		args.CERR()		<< "\t" << missing_identifiers.size()
						<< "\tsequence identifier"
						<< is_plural(missing_identifiers.size())
						<< " not found in the map.\n";
		if (args.file_unmatched_identifiers.size())
		{
			BOOST_FOREACH(const string& s, missing_identifiers.get_deque()) 
				f_unmatched_identifiers << s << "\n";
			args.VERBOSE()		<< "\t" << args.file_unmatched_identifiers
							<< "contains the missing identifiers. Unmatching lines were discarded.\n";
		}				
	}
	args.VERBOSE()	<< "\t" << parse_pairs.cnt_pairs_input
					<< "\tsequence pair"
					<< is_plural(parse_pairs.cnt_pairs_input)
					<< " read\n";

	args.VERBOSE()	<< "\t" << parse_pairs.cnt_pairs_output
					<< "\tsequence pair"
					<< is_plural(parse_pairs.cnt_pairs_output)
					<< " written\n";


}




//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//		Main

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
int main(int argc, char* argv[])
{
	t_program_args args;

	try
	{
		ofstream f_unmatched_identifiers;
		if (!args.file_unmatched_identifiers.empty())
		{
			f_unmatched_identifiers.open(args.file_unmatched_identifiers.c_str());
			if (!f_unmatched_identifiers)
				throw std::runtime_error("Could not open file [" + 
										 args.file_unmatched_identifiers +
										 "]\n");
		}

		debug_func_cerr(5);
	
		// don't use C streams
		std::ios::sync_with_stdio(false);
	
	
		// get command line arguments
		if (!process_command_line_options(argc, argv, args))
			return 1;
	
		//
		//	Get Identifier maps
		//
		t_index_string						ids_from;
		t_index_string						ids_to;
		std::deque<std::deque<unsigned> >	id_map;

		t_identifier_map map(ids_from, ids_to, args.comment_letters, 
							 args.from_format, args.to_format);
		map.parse_file(args.file_map, args.VERBOSE(0));


		deque<t_identifier_map::t_1_to_m>	lookup_1_to_m;
		deque<unsigned>						many_elements;

		unsigned count_duplicates = map.create_lookups(lookup_1_to_m,  many_elements);
		args.VERBOSE() << "\t" << count_duplicates << " duplicate entries in the lookup map.\n";



		// set all 1:m to 1:1
		if (args.first_instance)
			BOOST_FOREACH(t_1_to_m& lookup, lookup_1_to_m) 
			{
				assert(lookup.end != lookup.offset);
				lookup.end = lookup.offset + 1;
			}

		// set all 1:m to 1:0
		else if (args.ignore_duplicates)
		{
			BOOST_FOREACH(t_1_to_m& lookup, lookup_1_to_m) 
			{
				assert(lookup.end != lookup.offset);
				if (lookup.end > lookup.offset + 1)
					lookup.end = lookup.offset;
			}
		}
	
        //DEBUGG
		// dump map
		//for (unsigned i = 0; i < lookup_1_to_m.size(); ++i)
		//{
		//	t_1_to_m& lookup = lookup_1_to_m[i];
		//	//std::cerr << lookup.offset << "," << lookup.end - lookup.offset << "\n";
		//	for (unsigned jj = lookup.offset, end = lookup.end; jj != end; ++jj)
		//	{
		//		assert(jj < many_elements.size());
		//		std::cerr << ids_from[i] << "\t=>\t" << ids_to[many_elements[jj]] << "\n";
		//	}
        //}

		substitute_ids_from_file( ids_from,
								  ids_to,  
								  lookup_1_to_m,  
								  many_elements,
								  args.keep_non_matching_lines,
								  f_unmatched_identifiers,
								  args);   
	
		args.VERBOSE() << "\tAll finished.\n";

	}
	catch (std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return 1;
	}

	return 0;
}

